package com.pku.smart.trade.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.binarywang.wxpay.bean.result.WxPayBillInfo;
import com.opencsv.CSVReader;
import com.opencsv.ICSVParser;
import com.opencsv.bean.ColumnPositionMappingStrategy;
import com.opencsv.bean.CsvToBean;
import com.pku.smart.account.entity.AccountPayChannel;
import com.pku.smart.account.service.IAccountPayChannelService;
import com.pku.smart.config.AliPayConfig;
import com.pku.smart.constant.PayConstant;
import com.pku.smart.exception.BizException;
import com.pku.smart.fiance.entity.FianceAl;
import com.pku.smart.fiance.entity.FianceWx;
import com.pku.smart.fiance.service.IFianceAlService;
import com.pku.smart.fiance.service.IFianceWxService;
import com.pku.smart.log.MyLog;
import com.pku.smart.trade.channel.PayChannelFactory;
import com.pku.smart.trade.channel.PayChannelService;
import com.pku.smart.trade.service.IPaymentFianceService;
import com.pku.smart.trade.vopackage.VoReqDownBill;
import com.pku.smart.trade.vopackage.VoResDownBill;
import com.pku.smart.trade.vopackage.VoResTradePayment;
import com.pku.smart.utils.ZipUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.*;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Service
public class PaymentFianceServiceImpl implements IPaymentFianceService {

    private static final MyLog _log = MyLog.getLog(PaymentFianceServiceImpl.class);

    private PayChannelFactory factory = new PayChannelFactory();

    @Autowired
    IAccountPayChannelService payChannelService;

    @Autowired
    IFianceAlService fianceAlService;

    @Autowired
    IFianceWxService fianceWxService;

    @Autowired
    AliPayConfig aliPayConfig;

    @Override
    /**
     * 对账单下载
     * @param requestVo
     * @return
     */
    public VoResTradePayment downBill(VoReqDownBill requestVo) {
        String mchId = requestVo.getMchId();
        String channelName = requestVo.getChannelName();
        _log.info("根据渠道名称{}查询任一支付渠道",channelName);
        List<AccountPayChannel> channelList = payChannelService.getPayChannelList(mchId,channelName);
        if (CollectionUtils.isEmpty(channelList)){
            throw new BizException("根据商户编码未找到支付渠道");
        }
        AccountPayChannel payChannel = channelList.get(0);
        String channelMchId = payChannel.getChannelMchId();
        _log.info("构建支付渠道");
        PayChannelService service = factory.build(payChannel.getMchId(), payChannel.getChannelId());

        VoResDownBill responseVo = new VoResDownBill();
        responseVo.setMchId(requestVo.getMchId());
        responseVo.setChannelName(requestVo.getChannelName());
        responseVo.setBillDate(requestVo.getBillDate());

        _log.info("渠道执行");
        Map tradeMap = service.downbill(requestVo);
        VoResTradePayment tradeResult = new VoResTradePayment();
        responseVo.setErrCode((String) tradeMap.get("errCode"));
        responseVo.setErrCodeDes((String) tradeMap.get("errMsg"));
        String retCode = (String) tradeMap.getOrDefault("retCode", "");
        _log.info("渠道执行状态：" + retCode);
        if (!PayConstant.TRADE_STATUS_SUCCESS.equalsIgnoreCase(retCode)) {
            responseVo.setBillRecords(0);
            tradeResult.setRetCode(PayConstant.TRADE_STATUS_FAILED);
            tradeResult.setRetData(responseVo);
            return tradeResult;
        }

        //解析保存对账单
        String fileName = channelMchId + "_" + requestVo.getBillDate().replaceAll("-","") + "_csv.zip";//20881021698211630156_20210210.csv.zip
        if (PayConstant.CHANNEL_NAME_ALIPAY.equals(channelName)){
            _log.info("支付宝>>>" + fileName);
            String billDownloadUrl = (String)tradeMap.get("billDownloadUrl");
            _log.info("获取支付宝对账单成功，下载地址：" + billDownloadUrl);
            int count = saveAlBill(billDownloadUrl,mchId,channelMchId,requestVo.getBillDate(),fileName);
            tradeResult.setRetCode(PayConstant.TRADE_STATUS_SUCCESS);
            responseVo.setBillRecords(count);
        } else if (PayConstant.CHANNEL_NAME_WX.equals(channelName)){
            _log.info("微信>>>");
            List<WxPayBillInfo> list = (List<WxPayBillInfo>)tradeMap.get("billData");
            int count = saveWxBill(list,mchId,channelMchId,requestVo.getBillDate());
            tradeResult.setRetCode(PayConstant.TRADE_STATUS_SUCCESS);
            responseVo.setBillRecords(count);
        } else {
            tradeResult.setRetCode(PayConstant.TRADE_STATUS_FAILED);
            responseVo.setBillRecords(0);
            responseVo.setErrCode("SYSTEM ERROR");
            responseVo.setErrCodeDes("无效的支付渠道名称：" + channelName);
        }

        tradeResult.setRetData(responseVo);

        _log.info("打印：" + JSON.toJSONString(tradeResult));
        return tradeResult;
    }

    /**
     * 保存微信对账单
     * @param list
     * @param mchId
     * @param channelMchId
     * @param billDate
     * @return
     */
    private int saveWxBill(List<WxPayBillInfo> list,String mchId,String channelMchId,String billDate){
        Date deleteDate = null;
        try {
            deleteDate = DateUtils.parseDate(billDate,"yyyy-MM-dd");
        } catch (ParseException e) {
            e.printStackTrace();
            _log.error("日期格式化失败" + e.getMessage());
            throw new RuntimeException(e.getMessage());
        }
        _log.info("保存前删除旧记录");
        fianceWxService.deleteWx(mchId,deleteDate);
        _log.info("准备重新插入{}条。", list.size());
        for (int i = 0; i < list.size(); i++) {
            WxPayBillInfo baseResult = list.get(i);
            _log.info("记录：{}",JSONObject.toJSONString(baseResult));
            FianceWx fianceWx = new FianceWx();
            fianceWx.setDate(deleteDate);
            fianceWx.setTime(baseResult.getTradeTime());
            fianceWx.setGhid(baseResult.getAppId());//公众账号ID
            fianceWx.setMchId(mchId);//平台商户号
            fianceWx.setMchid(mchId);//兼容
            fianceWx.setMerno(baseResult.getSubMchId());//微信商户号
            fianceWx.setSubmch(baseResult.getMchId());//微信子商户号
            fianceWx.setDeviceid(baseResult.getDeviceInfo());//设备号
            fianceWx.setWxorder(baseResult.getTransactionId());//微信订单号
            fianceWx.setBzorder(baseResult.getOutTradeNo());//商户订单号
            fianceWx.setOpenid(baseResult.getOpenId());//用户标识
            fianceWx.setTradetype(baseResult.getTradeType());//交易类型
            fianceWx.setTradestatus(baseResult.getTradeState());//交易状态
            fianceWx.setBank(baseResult.getBankType());//付款银行
            fianceWx.setCurrency(baseResult.getFeeType());//货币种类
            fianceWx.setTotalmoney(new BigDecimal(baseResult.getTotalFee()));//总金额
            fianceWx.setRedpacketmoney(new BigDecimal(baseResult.getCouponFee()));//企业红包总金额
            fianceWx.setWxrefundorder(baseResult.getRefundId());//
            fianceWx.setBzrefundorder(baseResult.getOutRefundNo());//
            fianceWx.setRefundmoney(new BigDecimal(baseResult.getSettlementRefundFee()));//退款金额
            fianceWx.setRedpacketrefundmoney(new BigDecimal(baseResult.getCouponRefundFee()));//企业红包退款金额
            fianceWx.setRefundtype(baseResult.getRefundChannel());//
            fianceWx.setRefundstatus(baseResult.getRefundState());//
            fianceWx.setCommodityname(baseResult.getBody());//商品名称
            fianceWx.setDatapacket(baseResult.getAttach());//商户数据包
            fianceWx.setFee(new BigDecimal(baseResult.getPoundage()));//手续费
            fianceWx.setRate(baseResult.getPoundageRate());//费率
            fianceWx.setChannelName("WX");
            fianceWx.setCreateBy(PayConstant.TRADE_DEFAULT_OPERA);
            fianceWx.setCreateTime(new Date());
            fianceWxService.save(fianceWx);
        }
        LambdaQueryWrapper<FianceWx> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(FianceWx::getMchId,mchId);
        queryWrapper.eq(FianceWx::getDate,deleteDate);
        return fianceWxService.count(queryWrapper);
    }

    /**
     * 保存支付宝对账单
     * @param billDownloadUrl
     * @param mchId
     * @param channelMchId
     * @param billDate
     * @param fileName
     * @return
     */
    private int saveAlBill(String billDownloadUrl,String mchId,String channelMchId,String billDate,String fileName){
        try {
            _log.info("开始下载文件：" + fileName);
            String cvsFile = downBillFile(billDownloadUrl,channelMchId,billDate,fileName);
            _log.info("准备解析对账文件。{}",cvsFile);
            File fileCvs = new File(cvsFile);
            _log.info(fileCvs.getName());
            InputStreamReader fReader = new InputStreamReader(new FileInputStream(fileCvs),"GBK");
            _log.info("跳过前面5行，正式数据从第6行开始。");
            CSVReader csvReader = new CSVReader(fReader,',', '"', '#', 5,
                    ICSVParser.DEFAULT_STRICT_QUOTES);
            Date deleteDate = DateUtils.parseDate(billDate,"yyyy-MM-dd");
            _log.info("删除原对账记录。");
            fianceAlService.deleteAl(mchId,deleteDate);
            ColumnPositionMappingStrategy<FianceAl> mapper = new ColumnPositionMappingStrategy<FianceAl>();
            mapper.setType(FianceAl.class);
            CsvToBean<FianceAl> csv = new CsvToBean<FianceAl>();
            List<FianceAl> fianceAlList = new ArrayList<>();
            List<FianceAl> list = csv.parse(mapper, csvReader);
            for (int i = 0 ; i <= list.size() - 1 ; i ++){
                FianceAl fianceAl = list.get(i);
                fianceAl.setDate(deleteDate);
                fianceAl.setMchId(mchId);
                fianceAl.setChannelName(PayConstant.CHANNEL_NAME_ALIPAY);
                fianceAl.setCreateTime(new Date());
                fianceAl.setCreateBy(PayConstant.TRADE_DEFAULT_OPERA);
                //部分关键字段存在空格
                if (StringUtils.isNotBlank(fianceAl.getAlorder())) {
                    fianceAl.setAlorder(fianceAl.getAlorder().trim());
                }
                if (StringUtils.isNotBlank(fianceAl.getBzorder())) {
                    fianceAl.setBzorder(fianceAl.getBzorder().trim());
                }
                if (StringUtils.isNotBlank(fianceAl.getTradetype())) {
                    fianceAl.setTradetype(fianceAl.getTradetype().trim());
                }
                if (StringUtils.isNotBlank(fianceAl.getTforder())) {
                    fianceAl.setTforder(fianceAl.getTforder().trim());
                }
                _log.info("保存支付宝对账记录。" + JSONObject.toJSON(fianceAl));
                if (StringUtils.isNotBlank(fianceAl.getBzorder())) {
                    //fianceAlService.save(fianceAl);
                    fianceAlList.add(fianceAl);
                }
            }
            csvReader.close();
            _log.info("需要重新保存的记录数：" + fianceAlList.size());
            fianceAlService.saveBatch(fianceAlList);
            //LambdaQueryWrapper<FianceAl> queryWrapper = new LambdaQueryWrapper<>();
            //queryWrapper.eq(FianceAl::getMchId,mchId);
            //queryWrapper.eq(FianceAl::getDate,deleteDate);
            //return fianceAlService.count(queryWrapper);
            return fianceAlList.size();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            throw new RuntimeException("UnsupportedEncodingException异常：" + e.getMessage());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("FileNotFoundException异常：" + e.getMessage());
        } catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("Exception异常：" + e.getMessage());
        }
    }

    /**
     * 下载对账单
     * @param billDownloadUrl 支付宝对账单下载地址
     * @param fileName 下载原始文件名
     * @return 解压后的csv文件名
     */
    private String downBillFile(String billDownloadUrl, String channelMchId, String dateStr, String fileName) {
        String billPath = aliPayConfig.getBillPath();
        File saveDir = new File(billPath);
        _log.info("支付宝账单下载目录：{}",billPath);
        URL url = null;
        HttpURLConnection httpUrlConnection = null;
        InputStream fis = null;
        FileOutputStream fos = null;
        try {
            if (!saveDir.exists()) {
                saveDir.mkdir();
            }
            File file = new File(saveDir + File.separator + fileName);
            file.deleteOnExit();

            url = new URL(billDownloadUrl);
            httpUrlConnection = (HttpURLConnection) url.openConnection();
            httpUrlConnection.setConnectTimeout(15 * 1000);
            httpUrlConnection.setDoInput(true);
            httpUrlConnection.setDoOutput(true);
            httpUrlConnection.setUseCaches(false);
            httpUrlConnection.setRequestMethod("GET");
            httpUrlConnection.setRequestProperty("CHARSET", "UTF-8");
            httpUrlConnection.connect();
            fis = httpUrlConnection.getInputStream();
            byte[] temp = new byte[1024];
            int b;
            fos = new FileOutputStream(file);
            while ((b = fis.read(temp)) != -1) {
                fos.write(temp, 0, b);
                fos.flush();
            }
            if (file.exists()) {
                String downFile = file.toPath().toString();
                String downPath = billPath + File.separator;
                _log.info("开始解压" + downFile + "到" + downPath);
                ZipUtils.unZip(downFile, downPath);
                _log.info("对账文件解压完毕。");
            } else {
                throw new RuntimeException("没有下载到对账文件." + file.getName());
            }
        } catch (MalformedURLException e) {
            _log.error("解压对账文件异常MalformedURLException：" + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            _log.error("解压对账文件异常IOException：" + e.getMessage());
            e.printStackTrace();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (fos != null) {
                    fos.close();
                }
                if (httpUrlConnection != null) {
                    httpUrlConnection.disconnect();
                }
            } catch (IOException e) {
                _log.error("解压对账文件异常IOException：" + e.getMessage());
                e.printStackTrace();
            }
        }
        String cvsFile = saveDir + File.separator + channelMchId + "_" + dateStr.replaceAll("-", "") + "_业务明细.csv";
        File csv = new File(cvsFile);
        if (!csv.exists()) {
            _log.error("对账文件{}未找到，可能解析有问题，请重新下载",cvsFile);
            throw new BizException("未找到对账文件，请重新下载！");
        }
        return  cvsFile;
    }
}
